//
//  ZQQDownLoader.m
//  ZQQDownLoader
//
//  Created by MoDao-iOS on 2017/7/17.
//  Copyright © 2017年 MoDao-iOS. All rights reserved.
//

#import "ZQQDownLoader.h"
#import "ZQQFileTool.h"
#import "NSString+ZQQMD5.h"

#define kCachePath NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES).firstObject
#define kTmpPath NSTemporaryDirectory()

@interface ZQQDownLoader()<NSURLSessionDataDelegate>
{
    // 记录文件临时下载大小
    long long _tmpSize;
    // 记录文件总大小
    long long _totalSize;
}
/** 下载的会话 */
@property (nonatomic, strong) NSURLSession *session;
/** 下载完成路径 */
@property (nonatomic, copy) NSString *downLoadedPath;
/** 下载临时路径 */
@property (nonatomic, copy) NSString *downLoadingPath;
/** 文件输出流 */
@property (nonatomic, strong) NSOutputStream *outputStream;
/** 当前下载任务 */ // 当前属性使用weak是因为他是session持有，并不是当前类持有
@property (nonatomic, weak) NSURLSessionDataTask *dataTask;

@property (nonatomic, weak) NSURL *url;
@end

@implementation ZQQDownLoader

+ (NSString *)downLoadedFileWithURL:(NSURL *)url {
    NSString *cacheFilePath = [kCachePath stringByAppendingPathComponent:url.lastPathComponent];
    
    if ([ZQQFileTool fileExists:cacheFilePath]) {
        return cacheFilePath;
    }
    return nil;
}

+ (long long)tmpCacheSizeWithURL:(NSURL *)url {
    NSString *tmpFileMd5 = [url.absoluteString md5];
    NSString *tmpPath = [kTmpPath stringByAppendingString:tmpFileMd5];
    return [ZQQFileTool fileSize:tmpPath];
}

+ (void)clearCacheWithURL:(NSURL *)url {
    NSString *cachePath = [kCachePath stringByAppendingPathComponent:url.lastPathComponent];
    [ZQQFileTool removeFile:cachePath];
}

- (void)downLoader:(NSURL *)url downLoadInfo:(DownLoadInfoType)downLoadInfo progress:(ProgressBlockType)progressBlock success:(SuccessBlockType)successBlock failed:(FailedBlockType)failedBlock {
    // 1. 给所有的block赋值
    self.downLoadInfo = downLoadInfo;
    self.progressChange = progressBlock;
    self.successBlock = successBlock;
    self.faildBlock = failedBlock;
    
    // 2. 开始下载
    [self downLoader:url];
}

- (void)downLoader:(NSURL *)url {
    self.url = url;
    // 内部实现
    // 1. 真正的从头开始下载
    // 2. 如果任务存在了, 继续下载
    
    // 0. 当前任务, 肯定存在
    if ([url isEqual:self.dataTask.originalRequest.URL]) {
        // 下载失败就不能进入
        // 判断当前的状态, 如果是暂停状态
        if (self.state == ZQQDownLoaderStatePause) {
            // 继续
            [self resumeCurrentTask];
            return;
        }
    }
    
    // 两种: 1. 任务不存在, 2. 任务存在, 但是, 任务的Url地址 不同
    [self cacelCurrentTask];
    
    // 1. 文件的存放
    // 下载ing => temp + 名称
    // MD5 + URL 防止重复资源
    // a/1.png md5 -
    // b/1.png
    // 下载完成 => cache + 名称
    NSString *fileName = url.lastPathComponent;
    
    self.downLoadedPath = [kCachePath stringByAppendingPathComponent:fileName];
    self.downLoadingPath = [kTmpPath stringByAppendingPathComponent:fileName];
    
    // 1. 判断, url地址, 对应的资源, 是下载完毕,(下载完成的目录里面,存在这个文件)
    // 1.1 告诉外界, 下载完毕, 并且传递相关信息(本地的路径, 文件的大小)
    //     return
    if ([ZQQFileTool fileExists:self.downLoadedPath]) {
        // UNDO: 告诉外界, 已经下载完成;
        self.state = ZQQDownLoaderStateSuccess;
        return;
    }
    
    // 2. 检测, 临时文件是否存在
    // 2.2 不存在: 从0字节开始请求资源
    //     return
    if (![ZQQFileTool fileExists:self.downLoadingPath]) {
        // 从0字节开始请求资源
        [self downLoadWithURL:url offset:0];
        return;
    }
    
    // 2.1 存在, : 直接, 以当前的存在文件大小, 作为开始字节, 去网络请求资源
    //     HTTP: rang: 开始字节-
    //    正确的大小 1000   1001
    
    //   本地大小 == 总大小  ==> 移动到下载完成的路径中
    //    本地大小 > 总大小  ==> 删除本地临时缓存, 从0开始下载
    //    本地大小 < 总大小 => 从本地大小开始下载
    
    // 获取本地大小
    _tmpSize = [ZQQFileTool fileSize:self.downLoadingPath];
    [self downLoadWithURL:url offset:_tmpSize];
}

- (void)pauseCurrentTask {
    if (self.state == ZQQDownLoaderStateDowning) {
        self.state = ZQQDownLoaderStatePause;
        [self.dataTask suspend];
    }
}

// 继续任务
- (void)resumeCurrentTask {
    // 任务存在
    if (self.dataTask && self.state == ZQQDownLoaderStatePause) {
        [self.dataTask resume];
        self.state = ZQQDownLoaderStateDowning;
    }
}

- (void)cacelCurrentTask {
    self.state = ZQQDownLoaderStatePause;
    [self.session invalidateAndCancel];
    self.session = nil;
}

- (void)cacelAndClean {
    [self cacelCurrentTask];
    [ZQQFileTool removeFile:self.downLoadingPath];
    // 下载完成的文件 -> 手动删除某个声音 -> 统一清理缓存
}

#pragma mark ====NSURLSessionDataDelegate
// 第一次接受到相应的时候调用(响应头, 并没有具体的资源内容)
// 通过这个方法, 里面, 系统提供的回调代码块, 可以控制, 是继续请求, 还是取消本次请求
-(void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSHTTPURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler {
    // Content-Length 请求的大小 != 资源大小
    // 取资源总大小
    // 1. 从  Content-Length 取出来
    // 2. 如果 Content-Range 有, 应该从Content-Range里面获取
    _totalSize = [response.allHeaderFields[@"Content-Length"] longLongValue];
    NSString *contentRangeStr = response.allHeaderFields[@"Content-Range"];
    if (contentRangeStr.length != 0) {
        _totalSize = [[contentRangeStr componentsSeparatedByString:@"/"].lastObject longLongValue];
    }
    
    // 向外界传递下载信息
    if (self.downLoadInfo) {
        self.downLoadInfo(_totalSize);
    }
    
    // 比对本地大小, 和 总大小
    if (_tmpSize == _totalSize) {
        // 1. 移动到下载完成文件夹
        self.state = ZQQDownLoaderStateSuccess;
        [ZQQFileTool moveFile:self.downLoadingPath toPath:self.downLoadedPath];
        
        // 2. 取消本次请求
        completionHandler(NSURLSessionResponseCancel);
        return;
    }
    
    if (_tmpSize > _totalSize) {
        
        // 1. 删除临时缓存
        [ZQQFileTool removeFile:self.downLoadingPath];
        
        // 2. 取消请求
        completionHandler(NSURLSessionResponseCancel);
        
        // 3. 从0 开始下载
        [self downLoader:response.URL];
        return;
    }
    
    // 继续接受数据
    // 确定开始下载数据
    self.state = ZQQDownLoaderStateDowning;
    self.outputStream = [NSOutputStream outputStreamToFileAtPath:self.downLoadingPath append:YES];
    [self.outputStream open];
    completionHandler(NSURLSessionResponseAllow);
}

// 当用户确定, 继续接受数据的时候调用
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {
    // 这就是当前已经下载的大小
    _tmpSize += data.length;
    self.progress = 1.0 * _tmpSize / _totalSize;
    [self.outputStream write:data.bytes maxLength:data.length];
}

// 请求完成的时候调用( != 请求成功/失败)
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
        if (error == nil) {
        // 不一定是成功
        // 数据是肯定可以请求完毕
        // 判断, 本地缓存 == 文件总大小 {filename: filesize: md5:xxx}
        // 如果等于 => 验证, 是否文件完整(file md5 )
        [ZQQFileTool moveFile:self.downLoadingPath toPath:self.downLoadedPath];
        self.state = ZQQDownLoaderStateSuccess;
    }else {
        // -999 ====cancelled
        // -1005 ====The network connection was lost.
        NSLog(@"有问题 --- %zd ====%@",error.code, error.localizedDescription);
        // 取消， 断网
        self.state = ZQQDownLoaderStateFailed;
    }
    // 关闭输入流
    [self.outputStream close];
}

#pragma mark - 私有方法

/**
 根据开始字节, 请求资源
 
 @param url url
 @param offset 开始字节
 */
- (void)downLoadWithURL:(NSURL *)url offset:(long long)offset {

    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:0];
    // 通过控制range, 控制请求资源字节区间
    [request setValue:[NSString stringWithFormat:@"bytes=%lld-", offset] forHTTPHeaderField:@"Range"];
    // session 分配的task, 默认情况, 挂起状态
    self.dataTask = [self.session dataTaskWithRequest:request];
    [self resumeCurrentTask];
}

#pragma mark ====懒加载
- (NSURLSession *)session {
    if (!_session) {
        NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
        _session = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:[NSOperationQueue mainQueue]];
    }
    return _session;
}

#pragma mark ====事件/数据传递
- (void)setState:(ZQQDownLoaderState)state {
    // 1.数据过滤
    if (_state == state) {
        return;
    }
    
    _state = state;
    // 代理  通知 block告诉外界（推模式）
    if (self.stateChange) {
        self.stateChange(state);
    }
    if (_state == ZQQDownLoaderStateSuccess && self.successBlock) {
        self.successBlock(self.downLoadedPath);
    }
    
    if (_state == ZQQDownLoaderStateFailed && self.faildBlock) {
        self.faildBlock();
    }
    
    // 发布通知，下载状态变化
    [[NSNotificationCenter defaultCenter] postNotificationName:kDownLoadURLOrStateChangeNotification object:nil userInfo:@{@"downLoadURL": self.url, @"downLoadState": @(state)}];
}

- (void)setProgress:(float)progress {
    _progress = progress;
    if (self.progressChange) {
        self.progressChange(_progress);
    }
}
@end
